package com.passion.aspect;

import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson2.JSON;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * controller访问日志切面
 * 注意，execution中的包路径！
 *
 * @author 🇨🇳中国印🇨🇳 zyexplorej2ee@sina.com
 * @since 2022年12月01日
 */
@Component
@Aspect
@Slf4j
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class ProjectLogAspect {

    /**
     * 需要控制日志打印的接口集合
     */
    private final List<String> controlPrintList = Arrays.asList("/messageNotice/myNoticesList", "/common/checkSessionExpired");

    /**
     * 对要控制打印的接口的计数器集合
     */
    private final Map<String, Long> countMap = Maps.newHashMap();

    /**
     * 切面：web层控制器controller中所有方法
     */
    @Pointcut("execution(* com.passion.*..*Controller.*(..))")
    public void webLog() {
    }

    @Value("${spring.profiles.active:}")
    private String profilesActive;

    /**
     * 后置通知
     *
     * @param ret 方法返回对象
     */
    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) {

    }

    /**
     * 统计切面方法执行时间
     *
     * @author 🇨🇳中国印🇨🇳 zyexplorej2ee@sina.com
     * @since 2022年01月17日 21:40:46
     */
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        // result 为方法的返回值
        Object result = pjp.proceed();
        long end = System.currentTimeMillis();
        // 获取请求、响应对象
        ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        // 只对外部request进行处理
        if (attrs != null) {
            HttpServletRequest request = attrs.getRequest();
            HttpServletResponse response = attrs.getResponse();
            String uri = request.getRequestURI();
            String clientIp = ServletUtil.getClientIP(request);

            Object[] args = pjp.getArgs();
            // 对入参中带有 user 的接口，将二进制图片数据置空后打印日志
            String argsStr = this.dealWithArgs(uri, args);
            // 对返回中带有 user 的，将二进制图片数据置空后打印日志
            String retObjStr = this.dealWithResult(result);
            // 根据环境适当缩短打印的串
            ShortStrResult shortStrResult = this.getShortStrResult(argsStr, retObjStr);

            boolean flag = controlPrintList.stream().anyMatch(uri::endsWith);
            int status = 200;
            if (response != null) {
                status = response.getStatus();
            }
            if (flag) {
                if (countMap.containsKey(uri)) {
                    countMap.put(uri, countMap.get(uri) + 1);
                } else {
                    countMap.put(uri, 1L);
                }

                // 对控制打印的接口，每50次打印一次
                int controlPrintCount = 50;
                if (countMap.get(uri) % controlPrintCount == 0) {
                    // 每50次打印一次
                    log.info("耗时:{}ms, 请求IP:{}, 状态码:{}, 请求URL:{} 请求参数：{} 返回值：{}", (end - start), clientIp, status, request.getRequestURL(), shortStrResult.argsStr, shortStrResult.retObjStr);
                }
            } else {
                // 不需要控制打印的，直接打印
                log.info("耗时:{}ms, 请求IP:{}, 状态码:{}, 请求URL:{} 请求参数：{} 返回值：{}", (end - start), clientIp, status, request.getRequestURL(), shortStrResult.argsStr, shortStrResult.retObjStr);
            }
            return result;
        }
        return true;
    }

    private ShortStrResult getShortStrResult(String argsStr, String retObjStr) {
        AtomicInteger strLength = new AtomicInteger(100);
        // 只要配置里包含本地或开发标识，则切面日志打印多些
        List<String> envList = Arrays.asList("local", "dev", "noLogFile");
        Arrays.stream(profilesActive.split(",")).forEach(env -> {
            if (envList.contains(env)) {
                strLength.set(10000);
            }
        });
        if (!StringUtils.isEmpty(argsStr) && argsStr.length() > strLength.get()) {
            argsStr = argsStr.substring(0, strLength.get());
        }
        if (!StringUtils.isEmpty(retObjStr) && retObjStr.length() > strLength.get()) {
            retObjStr = retObjStr.substring(0, strLength.get());
        }
        return new ShortStrResult(argsStr, retObjStr);
    }

    private static class ShortStrResult {
        public final String argsStr;
        public final String retObjStr;

        public ShortStrResult(String argsStr, String retObjStr) {
            this.argsStr = argsStr;
            this.retObjStr = retObjStr;
        }
    }

    private String dealWithResult(Object result) {
        if (result == null) {
            return "";
        }
        String retObjStr = JSON.toJSONString(result);
        return retObjStr;
    }

    private String dealWithArgs(String uri, Object[] args) {
        String argsStr = "";
        if (args != null) {
            argsStr = Arrays.toString(args);
        }
        return argsStr;

    }
}